import assert from 'node:assert'

import { fromCallback } from 'promise-toolbox'
import { v4 as uuid } from 'uuid'
import defaultsDeep from 'lodash.defaultsdeep'

import { DEFAULT_VBD } from './templates/vbd.mjs'
import { DEFAULT_VDI } from './templates/vdi.mjs'
import { DEFAULT_VIF } from './templates/vif.mjs'
import { DEFAULT_VM } from './templates/vm.mjs'
import toOvaXml from './_toOvaXml.mjs'
import { XVA_DISK_CHUNK_LENGTH } from './_writeDisk.mjs'

export default async function writeOvaXml(
  pack,
  { memory, networks, nCpus, firmware, vdis, vhds, ...vmSnapshot },
  { sr, network }
) {
  let refId = 0
  function nextRef() {
    return 'Ref:' + String(refId++).padStart(3, '0')
  }
  const data = {
    version: {
      hostname: 'localhost',
      date: '2022-01-01',
      product_version: '8.2.1',
      product_brand: 'XCP-ng',
      build_number: 'release/yangtze/master/58',
      xapi_major: 1,
      xapi_minor: 20,
      export_vsn: 2,
    },
    objects: [],
  }
  const vm = defaultsDeep(
    {
      id: nextRef(),
      // you can pass a full snapshot and nothing more to do
      snapshot: vmSnapshot,
    },
    {
      // some data need a little more work to be usable
      // if they are not already in vm
      snapshot: {
        HVM_boot_params: {
          firmware,
        },
        memory_static_max: memory,
        memory_static_min: memory,
        memory_dynamic_max: memory,
        memory_dynamic_min: memory,
        other_config: {
          mac_seed: uuid(),
        },
        uuid: uuid(),
        VCPUs_at_startup: nCpus,
        VCPUs_max: nCpus,
      },
    },
    DEFAULT_VM
  )

  data.objects.push(vm)
  const srObj = defaultsDeep(
    {
      class: 'SR',
      id: nextRef(),
      snapshot: sr,
    },
    {
      snapshot: {
        VDIs: [],
      },
    }
  )

  data.objects.push(srObj)
  assert.strictEqual(vhds.length, vdis.length)
  for (let index = 0; index < vhds.length; index++) {
    const userdevice = index + 1
    const vhd = vhds[index]
    const alignedSize = Math.ceil(vdis[index].virtual_size / XVA_DISK_CHUNK_LENGTH) * XVA_DISK_CHUNK_LENGTH
    const vdi = defaultsDeep(
      {
        id: nextRef(),
        // overwrite SR from an opaque ref to a ref:
        snapshot: { ...vdis[index], SR: srObj.id, virtual_size: alignedSize },
      },
      {
        snapshot: {
          uuid: uuid(),
        },
      },
      DEFAULT_VDI
    )

    data.objects.push(vdi)
    srObj.snapshot.VDIs.push(vdi.id)
    vhd.ref = vdi.id

    const vbd = defaultsDeep(
      {
        id: nextRef(),
        snapshot: {
          device: `xvd${String.fromCharCode('a'.charCodeAt(0) + index)}`,
          uuid: uuid(),
          userdevice,
          VM: vm.id,
          VDI: vdi.id,
        },
      },
      DEFAULT_VBD
    )
    data.objects.push(vbd)
    vdi.snapshot.vbds.push(vbd.id)
    vm.snapshot.VBDs.push(vbd.id)
  }

  if (network && networks?.length) {
    const networkObj = defaultsDeep(
      {
        class: 'network',
        id: nextRef(),
        snapshot: network,
      },
      {
        snapshot: {
          vifs: [],
        },
      }
    )
    data.objects.push(networkObj)
    let vifIndex = 0
    for (const sourceNetwork of networks) {
      const vif = defaultsDeep(
        {
          id: nextRef(),
          snapshot: {
            device: ++vifIndex,
            MAC: sourceNetwork.macAddress,
            MAC_autogenerated: sourceNetwork.isGenerated,
            uuid: uuid(),
            VM: vm.id,
            network: networkObj.id,
          },
        },
        DEFAULT_VIF
      )
      data.objects.push(vif)
      networkObj.snapshot.vifs.push(vif.id)
    }
  }
  const xml = toOvaXml(data)
  await fromCallback.call(pack, pack.entry, { name: `ova.xml` }, xml)
}
